#include "K_Representative.h"

#include <algorithm>
using std::for_each;

#include <limits>
using std::numeric_limits;

#include <iostream>
using::std::cout;
using std::endl;

#include <cmath>
using std::abs;

const int MAX_LABEL_BY_CATEGORY = 10;

K_Representative::K_Representative(int k,bool hybrid)
:_k(k),_hybrid(hybrid){}

vector<double> K_Representative::getMaxVector(const vector<Client>& data) const
{
	vector<double> max(data[0].getNumericVector().size(), numeric_limits<double>::min());
	for each (Client client in data)
	{
		for(int i=0; i<client.getNumericVector().size(); ++i)
		{
			if( client.getNumericVector()[i] > max[i] )
				max[i] = client.getNumericVector()[i];
		}
	}
	return max;
}

vector<double> K_Representative::getMinVector(const vector<Client>& data) const
{
	vector<double> min(data[0].getNumericVector().size(), numeric_limits<double>::max());
	for each (Client client in data)
	{
		for(int i=0; i<client.getNumericVector().size(); ++i)
		{
			if( client.getNumericVector()[i] < min[i] )
				min[i] = client.getNumericVector()[i];
		}
	}
	return min;
}

vector<Cluster> K_Representative::compute(const vector<Client>& data) const
{
	vector<Cluster> clusters = createRandomCluster(data);
	bool clustersCompositionChanged = true;

	vector<double> max;
	vector<double> min;

	if(_hybrid)
	{
		min = getMinVector(data);
		max = getMaxVector(data);
	}

	int cpt =0;
	while(clustersCompositionChanged)
	{
		cout << cpt++<< endl;
		vector<Cluster> reassignedClusters;
		reassignedClusters.resize(_k);

		clustersCompositionChanged = false;
		vector<ClusterRepresentative> QList;
		QList.reserve(_k);
		
		for_each( clusters.begin(), clusters.end(), [&] (const Cluster& cluster)
		{
			QList.push_back(ComputeQ(cluster));
		});

		set<int> numericAttributeIndexes;
		// averageForNumeric[cluster][attribute_index] = numeric attribute average
		vector<map<int, double>> clusterNumericValuesAverage;

		if(_hybrid)
		{
			numericAttributeIndexes = getNumericAttributeIndexes(QList);
			clusterNumericValuesAverage = getClusterNumericValuesAverage(clusters, numericAttributeIndexes);
		}

		for(unsigned int i=0; i<clusters.size(); ++i )
		{	
			for each( Client client in clusters[i])
			{
				unsigned int bestClusterIndex;
				double minDissimilarity = numeric_limits<double>::max( );
				for( unsigned int j=0; j<QList.size(); ++j)
				{
					double currentDissimilarity = _hybrid?  hybridDissimilarity(client, QList[j], clusterNumericValuesAverage[j], numericAttributeIndexes, min, max) : 
															dissimilarity(client, QList[j]);
					if ( currentDissimilarity < minDissimilarity)
					{
						bestClusterIndex = j;
						minDissimilarity = currentDissimilarity;
					}
				}

				if (bestClusterIndex != i)
				{
					clustersCompositionChanged = true;
				}
				reassignedClusters[bestClusterIndex].push_back(client);
			}
		}
		clusters = reassignedClusters;
	}

	return clusters;
}

vector<map<int, double>> K_Representative::getClusterNumericValuesAverage(const vector<Cluster>& clusters, const set<int>& numericAttributeIndexes)const
{
	vector<map<int, double>> clusterNumericValuesAverage;
	clusterNumericValuesAverage.resize(_k);

	for(int i=0; i<clusters.size(); ++i)
	{
		for each(int j in numericAttributeIndexes)
		{
			double average = 0.0;
			for(int k=0; k<clusters[i].size(); ++k)
				average += clusters[i][k].getNumericVector()[j];
			average /= clusters[i].size();
			clusterNumericValuesAverage[i][j] = average;
		}
	}
	return clusterNumericValuesAverage;
}

set<int> K_Representative::getNumericAttributeIndexes(vector<ClusterRepresentative>& QList)const
{
	set<int> indexOfNumericAttribute;
	// for all clusters
	for(unsigned int i=0; i<QList.size(); ++i)
	{
		// for all attibute
		for(unsigned int j=0; j<QList[i].size(); ++j)
		{
			if (QList[i][j].size() > MAX_LABEL_BY_CATEGORY)
			{
				indexOfNumericAttribute.insert(j);
			}
		}
	}
	return indexOfNumericAttribute;
}

vector<Cluster> K_Representative::createRandomCluster(const vector<Client>& data) const
{
	vector<Cluster> clusters;
	clusters.reserve(_k);
	int nbClientByCluster = data.size()/_k;
	for(int i = 0; i<_k; ++i)
		clusters.push_back( Cluster( data.begin() + (i*nbClientByCluster), data.begin() + ((i+1)*nbClientByCluster)) );

	//if data couldnt be divide perfectly in _k clusters 
	for(unsigned int i = nbClientByCluster*_k; i<data.size(); ++i )
		clusters[_k-1].push_back( data[i] );

	return clusters;
}

double K_Representative::dissimilarity(const Client& x, const ClusterRepresentative& q) const
{
	double summation = 0.0;
	for(unsigned int i=0;  i<x.getCategoricVector().size(); ++i)
	{
		auto fx = q[i].find(x.getCategoricVector()[i]);

		summation += 1.0 - ( fx != q[i].end() ? fx->second : 0.0);
	}
	return summation;
}

double K_Representative::hybridDissimilarity(const Client& x, const ClusterRepresentative& q, const map<int, double>& numericValuesAverage, const set<int>& numericAttributeIndexes, const vector<double>& min, const vector<double>& max) const
{
	double summation = 0.0;
	for(unsigned int i=0;  i<x.getNumericVector().size(); ++i)
	{
		if ( numericAttributeIndexes.count(i) )
		{
			double normalizeAverageDifference = abs (minMaxNormalization( x.getNumericVector()[i],  min[i],  max[i])  - minMaxNormalization( numericValuesAverage.find(i)->second,  min[i],  max[i]));
			summation += normalizeAverageDifference;
		}
		else
		{
			auto fx = q[i].find(x.getNumericVector()[i]);
			summation += 1.0 - ( fx != q[i].end() ? fx->second : 0.0);
		}
	}
	return summation;
}

double K_Representative::minMaxNormalization(const double& value, const double& min, const double& max) const
{
	return (value - min) / (max-min);
}


ClusterRepresentative K_Representative::ComputeQ(const Cluster& aCluster)const
{
	ClusterRepresentative retVal;

	if(aCluster.size() > 0)
	{
		retVal.resize(aCluster[0].getCategoricVector().size());
	}

	double ponderation = 1.0 / double(aCluster.size());
	
	for each (Client cl in aCluster)
	{
		vector<double> attributes = _hybrid? cl.getNumericVector() : cl.getCategoricVector();

		for(unsigned int i = 0; i < attributes.size() ; ++i)
		{
			qValue::iterator it = retVal[i].find(attributes[i]);
			
			if(it != retVal[i].end())
			{
				//element already exist
				it->second += ponderation;
			}
			else
			{
				retVal[i].insert( std::pair<double, double>(attributes[i], ponderation));
			}
		}
	}

	return retVal;
}